# AppEngine imports
from google.appengine.ext import db
from google.appengine.api import memcache
from google.appengine.ext.db import Key

# Python imports
import datetime
import logging
import hashlib
  
# Data models

class ddDeveloper(db.Model):
  """
  Represents a person, who may have several OpenSocial IDs.
  """
  name = db.StringProperty(default="Anonymous")
  opensocial_ids = db.StringListProperty()
  user = db.UserProperty()
  
  @staticmethod
  def register_account(account):
    dev = ddDeveloper.gql("WHERE opensocial_ids = :1 LIMIT 1",
                       account.opensocial_id).get()
    if not dev:
      dev = ddDeveloper()
      dev.opensocial_ids.append(account.opensocial_id)
      
    dev.name = account.name
    db.put(dev)
    
    return dev
  
  @property
  def samples(self):
    # TODO: Paging?
    return ddJavaScriptCodeSample.gql("WHERE developer = :1 ORDER BY updated DESC",
        self).fetch(100)

  @property
  def xmlspecs(self):
    # TODO: Paging?
    return ddXmlSpec.gql("WHERE developer = :1 ORDER BY updated DESC",
        self).fetch(100)
        
class ddOpenSocialAccount(db.Model):
  """
  Stores information about a given OpenSocial ID.
  """
  name = db.StringProperty()
  user_id = db.StringProperty()
  container = db.StringProperty()
  developer = db.ReferenceProperty(ddDeveloper)
  
  @property
  def opensocial_id(self):
    """
    Combines a container name and a user id into a global user identifier.
    """
    return ":".join([self.container, self.user_id])
    
  @staticmethod
  def get_key_name(container, user_id):
    """
    Returns a key for the given container and user id combination.
    """
    return "devaccount|%s|%s" % (container, user_id)

  @staticmethod
  def get_or_insert(container, user_id, name=None):
    """
    Gets a ddOpenSocialAccount object from the data store or creates a new one
    if none are returned. 
    """
    key_name = ddOpenSocialAccount.get_key_name(
        container=container, 
        user_id=user_id)
        
    dev_account = ddOpenSocialAccount.get_by_key_name(key_name)
    
    if not dev_account:  # Store the account info if we don't have it
      dev_account = ddOpenSocialAccount(key_name=key_name)
      dev_account.user_id = user_id
      dev_account.container = container
      if name is not None:
        # Not sure why this is breaking now, does Django automatically convert?
        # dev_account.name = unicode(name, "utf-8")
        dev_account.name = name
      dev = ddDeveloper.register_account(dev_account)
      dev_account.developer = dev
      db.put(dev_account)
    
      logging.info("Setting DevAccount for Container[%s], ID[%s], Name[%s]",
          container, user_id, name)
  
    if name is not None and dev_account.name != name:
      dev_account.name = name
      db.put(dev_account)    # Update the name if it has changed
      ddDeveloper.register_account(dev_account)
      
    return dev_account
    
  @property
  def samples(self):
    """
    Returns the ddJavaScriptCodeSamples for this developer account
    """
    if self.developer is not None:
      return self.developer.samples
    return None
    
  @property
  def xmlspecs(self):
    """
    Returns the ddXmlSpecs for this developer account
    """
    if self.developer is not None:
      return self.developer.xmlspecs
    return None
  

class ddXmlSpec(db.Model):
  """
  Represents parameters for a saved XML specification.  Stores the form values 
  as a JSON string.
  """
  name = db.StringProperty(default = "Unnamed spec")
  updated = db.DateTimeProperty(auto_now = True)
  developer = db.ReferenceProperty(ddDeveloper)
  text = db.TextProperty()

  @property
  def metadata(self):
    updated = self.updated.replace(tzinfo = UTC())
    return { "name" : self.name.encode('utf-8'),
             "id" : str(self.key()),
             "updated" : updated.strftime("%a, %d %b %Y %H:%M:%S %Z%z") }

  @property
  def data(self):
    updated = self.updated.replace(tzinfo = UTC())
    return { "name" : self.name.encode('utf-8'),
             "updated" : updated.strftime("%a, %d %b %Y %H:%M:%S %Z%z"),
             "developer" : self.developer.name,
             "text" : self.text.encode('utf-8'),
             "id" : str(self.key()) }
             
  @staticmethod
  def get_key_name(developer, name):
    """
    Returns a key for the given developer and spec name combination.
    """
    hash_name = hashlib.md5(name.encode('ascii', 'replace')).hexdigest()
    return "xmlspec|%s|%s" % (developer.key().id_or_name(), hash_name)
      
  @staticmethod
  def get_or_insert(developer, name):
    key_name = ddXmlSpec.get_key_name(
        developer=developer, 
        name=name)

    spec = ddXmlSpec.get_by_key_name(key_name)
  
    if spec is None:
      spec = ddXmlSpec(key_name=key_name)
      spec.developer = developer
      #spec.name = unicode(name, "utf-8")
      spec.name = name
      db.put(spec)
    
    return spec

class ddJavaScriptCodeSample(db.Model):
  """
  Represents a snippet of code that a developers can save.
  """
  name = db.StringProperty(default = "Unnamed sample")
  updated = db.DateTimeProperty(auto_now = True)
  developer = db.ReferenceProperty(ddDeveloper)
  text = db.TextProperty()
  description = db.TextProperty(default = "")
  is_global = db.BooleanProperty(default = False);
  
  @property
  def metadata(self):
    """
    Returns some metadata about the sample.
    """
    updated = self.updated.replace(tzinfo = UTC())
    return { "name" : self.name.encode('utf-8'),
             "id" : str(self.key()),
             "description" : self.description.encode('utf-8'),
             "updated" : updated.strftime("%a, %d %b %Y %H:%M:%S %Z%z") }
    
  @property
  def data(self):
    """
    Returns the full sample.
    """
    updated = self.updated.replace(tzinfo = UTC())
    return { "name" : self.name.encode('utf-8'),
             "updated" : updated.strftime("%a, %d %b %Y %H:%M:%S %Z%z"),
             "developer" : self.developer.name,
             "description" : self.description.encode('utf-8'),
             "text" : self.text.encode('utf-8'),
             "id" : str(self.key()),
             "is_global" : self.is_global }
        
  @staticmethod
  def get_key_name(developer, name):
    """
    Returns a key for the given developer and sample name combination.
    """
    hash_name = hashlib.md5(name.encode('ascii', 'replace')).hexdigest()
    return "jscodesample|%s|%s" % (developer.key().id_or_name(), hash_name)
        
  @staticmethod
  def get_or_insert(developer, name):
    key_name = ddJavaScriptCodeSample.get_key_name(
        developer=developer, 
        name=name)

    sample = ddJavaScriptCodeSample.get_by_key_name(key_name)
    
    if sample is None:
      sample = ddJavaScriptCodeSample(key_name=key_name)
      sample.developer = developer
      #sample.name = unicode(name, "utf-8")
      sample.name = name
      db.put(sample)
      
    return sample

class ddFlushedUrl(db.Model):
  """
  Represents a URL that has been flushed using the cache flush tool.
  """
  url = db.StringProperty()
  updated = db.DateTimeProperty(auto_now=True)

class ddSigningKey(db.Model):
  """
  Represents a key used to validate signed requests.
  """
  value = db.StringProperty()
  
  @staticmethod
  def get_key_name(consumer, method, name):
    """
    Returns the name of the key for the given arguments
    """
    return "signingkey|%s|%s|%s" % (method, consumer, name)
   
  @staticmethod
  def get(consumer, method, name):    
    """
    Returns a key, hopefully from memcache
    """
    key_name = ddSigningKey.get_key_name(
        method=method, 
        consumer=consumer, 
        name=name)
    logging.debug("Getting key [%s]", key_name)
    
    key = memcache.get(key_name)                         # look in memcache
    
    if key is None: 
      key = ddSigningKey.get_by_key_name(key_name)       # get the key
      memcache.set(key_name, key, 3600)                  # store in memcache
    
    if key is None:
      logging.warning("No key for Consumer[%s], Name[%s], Method[%s]",
                      consumer, name, method)            # warn on missing keys
                      
    return key
  
  @staticmethod
  def set(consumer, method, name, value):
    """
    Sets or updates a key value
    """
    key_name = ddSigningKey.get_key_name(
        method=method, 
        consumer=consumer, 
        name=name)
    key = ddSigningKey.get_or_insert(key_name, value=value)
    key.value = value
    key.put()
    memcache.set(key_name, key, 3600) 
    
    logging.info(
        "Setting key for Consumer[%s], Name[%s], Method[%s], Value[%s]",
        consumer, name, method, value)
  
class UTC(datetime.tzinfo):
  """
  A cheap hacky class that provides a tzinfo for datetime objects. ~JFisher
  """
  zero = datetime.timedelta(0)

  def utcoffset(self, dt):
    return self.zero

  def dst(self, dt):
    return self.zero

  def tzname(self, dt):
    return 'UTC'


  

  
